package cn.topcodes.tcsf.wechat.service.utils;

import cn.topcode.unicorn.utils.StringUtil;
import com.esotericsoftware.reflectasm.MethodAccess;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanUtils {

    private static Map<Object, MethodAccess> methodMap = new HashMap<Object, MethodAccess>();

    private static Map<String, Integer> methodIndexMap = new HashMap<String, Integer>();

    private static Map<Object, List<String>> fieldMap = new HashMap<Object, List<String>>();

    /**
     * 复制实体属性
     * @param desc
     * @param origin
     * @param isIgnoreNull  是否过滤null ""
     * @param ignoreProp    过滤属性
     */
    public static void copyProperties(Object desc, Object origin, boolean isIgnoreNull, List<String> ignoreProp) {
        MethodAccess descMethodAccess = methodMap.get(desc.getClass());
        if (descMethodAccess == null) {
            descMethodAccess = cache(desc, false, ignoreProp);
        }
        MethodAccess originMethodAccess = methodMap.get(origin.getClass());
        if (originMethodAccess == null) {
            originMethodAccess = cache(origin, isIgnoreNull, ignoreProp);
        }
        List<String> fieldList = fieldMap.get(origin);
        for (String field : fieldList) {
            String getKey = origin.getClass().getName() + "." + "get" + field;
            String setKey = origin.getClass().getName() + "." + "set" + field;
            Integer setIndex = methodIndexMap.get(setKey);
            if (setIndex != null) {
                Integer getIndex = methodIndexMap.get(getKey);
                descMethodAccess.invoke(desc, setIndex.intValue(),
                        originMethodAccess.invoke(origin, getIndex));
            }
        }
        clear();
    }

    /**
     * 默认过滤null  ""
     * @param desc
     * @param origin
     */
    public static void copyProperties(Object desc, Object origin) {
        copyProperties(desc, origin, true, null);
    }

    /**
     * 默认不过滤属性
     * @param desc
     * @param origin
     * @param isIgnoreNull
     */
    public static void copyProperties(Object desc, Object origin, boolean isIgnoreNull) {
        copyProperties(desc, origin, isIgnoreNull, null);
    }

    /**
     * Bean解析
     * @param origin        解析对象
     * @param isIgnoreNull  是否过滤 对象 == null String == null && String == ""
     * @param ignoreProp    过滤属性
     * @return
     */
    private static MethodAccess cache(Object origin, boolean isIgnoreNull, List<String> ignoreProp) {
        boolean isIgnoreProp = ignoreProp != null && !ignoreProp.isEmpty();
        synchronized (origin.getClass()) {
            MethodAccess methodAccess = MethodAccess.get(origin.getClass());
            Field[] fields = origin.getClass().getDeclaredFields();
            List<String> fieldList = new ArrayList<String>();
            for (Field field : fields) {
                if (Modifier.isPrivate(field.getModifiers())
                        && !Modifier.isStatic(field.getModifiers())) {
                    String fieldName = StringUtils.capitalize(field.getName());
                    if (isIgnoreProp && ignoreProp.contains(fieldName)) {
                        continue;
                    }
                    if (isIgnoreNull) {
                        if (!field.isAccessible()) {
                            field.setAccessible(true);
                        }
                        try {
                            Object value = field.get(origin);
                            if (value == null ||
                                    (value instanceof String && StringUtil.isBlank((String) value))) {
                                continue;
                            }
                        } catch (IllegalAccessException e) {
                            continue;
                        }
                    }
                    int getIndex = methodAccess.getIndex("get" + fieldName);
                    int setIndex = methodAccess.getIndex("set" + fieldName);
                    methodIndexMap.put(origin.getClass().getName() + "." + "get"
                            + fieldName, getIndex);
                    methodIndexMap.put(origin.getClass().getName() + "." + "set"
                            + fieldName, setIndex);
                    fieldList.add(fieldName);
                }
            }
            fieldMap.put(origin, fieldList);
            methodMap.put(origin, methodAccess);
            return methodAccess;
        }
    }

    private static void clear() {
        methodMap.clear();
        methodIndexMap.clear();
        fieldMap.clear();
    }
}
